use super::*;

pub struct MembersQObject;

pub struct MembersEmitter {
    pub(super) qobject: Arc<AtomicPtr<MembersQObject>>,
    pub(super) filter_changed: fn(*mut MembersQObject),
    pub(super) filter_regex_changed: fn(*mut MembersQObject),
    pub(super) new_data_ready: fn(*mut MembersQObject),
    pub(super) new_typing_indicator: fn(*mut MembersQObject),
}

impl MembersEmitter {
    /// Clone the emitter
    ///
    /// The emitter can only be cloned when it is mutable. The emitter calls
    /// into C++ code which may call into Rust again. If emmitting is possible
    /// from immutable structures, that might lead to access to a mutable
    /// reference. That is undefined behaviour and forbidden.
    pub fn clone(&mut self) -> MembersEmitter {
        MembersEmitter {
            qobject: self.qobject.clone(),
            filter_changed: self.filter_changed,
            filter_regex_changed: self.filter_regex_changed,
            new_typing_indicator: self.new_typing_indicator,
            new_data_ready: self.new_data_ready,
        }
    }

    pub fn clear(&self) {
        let n: *const MembersQObject = null();
        self.qobject
            .store(n as *mut MembersQObject, Ordering::SeqCst);
    }

    pub fn filter_changed(&mut self) {
        let ptr = self.qobject.load(Ordering::SeqCst);

        if !ptr.is_null() {
            (self.filter_changed)(ptr);
        }
    }

    pub fn filter_regex_changed(&mut self) {
        let ptr = self.qobject.load(Ordering::SeqCst);

        if !ptr.is_null() {
            (self.filter_regex_changed)(ptr);
        }
    }

    pub fn new_typing_indicator(&mut self) {
        let ptr = self.qobject.load(Ordering::SeqCst);

        if !ptr.is_null() {
            (self.new_typing_indicator)(ptr);
        }
    }

    pub fn new_data_ready(&mut self) {
        let ptr = self.qobject.load(Ordering::SeqCst);
        if !ptr.is_null() {
            (self.new_data_ready)(ptr);
        }
    }
}

#[derive(Clone)]
pub struct MembersList {
    pub(super) qobject: *mut MembersQObject,
    pub(super) layout_about_to_be_changed: fn(*mut MembersQObject),
    pub(super) layout_changed: fn(*mut MembersQObject),
    pub(super) begin_reset_model: fn(*mut MembersQObject),
    pub(super) end_reset_model: fn(*mut MembersQObject),
    pub(super) end_insert_rows: fn(*mut MembersQObject),
    pub(super) end_move_rows: fn(*mut MembersQObject),
    pub(super) end_remove_rows: fn(*mut MembersQObject),
    pub(super) begin_insert_rows: fn(*mut MembersQObject, usize, usize),
    pub(super) begin_remove_rows: fn(*mut MembersQObject, usize, usize),
    pub(super) data_changed: fn(*mut MembersQObject, usize, usize),
    pub(super) begin_move_rows: fn(*mut MembersQObject, usize, usize, usize),
}

impl MembersList {
    pub fn layout_about_to_be_changed(&mut self) {
        if !self.qobject.is_null() {
            (self.layout_about_to_be_changed)(self.qobject);
        }
    }

    pub fn layout_changed(&mut self) {
        if !self.qobject.is_null() {
            (self.layout_changed)(self.qobject)
        }
    }

    pub fn begin_reset_model(&mut self) {
        if !self.qobject.is_null() {
            (self.begin_reset_model)(self.qobject);
        }
    }

    pub fn end_reset_model(&mut self) {
        if !self.qobject.is_null() {
            (self.end_reset_model)(self.qobject);
        }
    }

    pub fn end_insert_rows(&mut self) {
        if !self.qobject.is_null() {
            (self.end_insert_rows)(self.qobject);
        }
    }

    pub fn end_move_rows(&mut self) {
        if !self.qobject.is_null() {
            (self.end_move_rows)(self.qobject);
        }
    }

    pub fn end_remove_rows(&mut self) {
        if !self.qobject.is_null() {
            (self.end_remove_rows)(self.qobject);
        }
    }

    pub fn begin_insert_rows(
        &mut self,
        first: usize,
        last: usize,
    ) {
        if !self.qobject.is_null() {
            (self.begin_insert_rows)(self.qobject, first, last);
        }
    }

    pub fn begin_remove_rows(
        &mut self,
        first: usize,
        last: usize,
    ) {
        if !self.qobject.is_null() {
            (self.begin_remove_rows)(self.qobject, first, last);
        }
    }

    pub fn data_changed(
        &mut self,
        first: usize,
        last: usize,
    ) {
        if !self.qobject.is_null() {
            (self.data_changed)(self.qobject, first, last);
        }
    }

    pub fn begin_move_rows(
        &mut self,
        first: usize,
        last: usize,
        destination: usize,
    ) {
        if !self.qobject.is_null() {
            (self.begin_move_rows)(self.qobject, first, last, destination);
        }
    }
}

pub trait MembersTrait {
    fn new(
        emit: MembersEmitter,
        model: MembersList,
    ) -> Self;

    fn emit(&mut self) -> &mut MembersEmitter;

    fn filter(&self) -> &str;

    fn set_filter(
        &mut self,
        value: String,
    );

    fn filter_regex(&self) -> bool;

    fn set_filter_regex(
        &mut self,
        value: bool,
    );

    fn add_to_conversation(
        &mut self,
        id: String,
    ) -> bool;

    fn remove_from_conversation_by_index(
        &mut self,
        row_index: u64,
    ) -> bool;

    fn toggle_filter_regex(&mut self) -> bool;

    fn typing_members(&self) -> String;

    fn row_count(&self) -> usize;

    fn insert_rows(
        &mut self,
        _row: usize,
        _count: usize,
    ) -> bool {
        false
    }

    fn remove_rows(
        &mut self,
        _row: usize,
        _count: usize,
    ) -> bool {
        false
    }

    fn can_fetch_more(&self) -> bool {
        false
    }

    fn fetch_more(&mut self) {}

    fn sort(
        &mut self,
        _: u8,
        _: SortOrder,
    ) {
    }

    fn last_typing(
        &self,
        index: usize,
    ) -> Option<i64>;

    fn matched(
        &self,
        index: usize,
    ) -> bool;

    fn user_id(
        &self,
        index: usize,
    ) -> &str;
}

#[no_mangle]
pub unsafe extern "C" fn members_new(ptr_bundle: *mut MembersPtrBundle) -> *mut Members {
    let d_members = members_new_inner(ptr_bundle);
    Box::into_raw(Box::new(d_members))
}

pub unsafe fn members_new_inner(ptr_bundle: *mut MembersPtrBundle) -> Members {
    let ptr_bundle = *ptr_bundle;

    let MembersPtrBundle {
        members,
        members_filter_changed,
        members_filter_regex_changed,
        members_new_data_ready,
        members_layout_about_to_be_changed,
        members_layout_changed,
        members_data_changed,
        members_begin_reset_model,
        members_end_reset_model,
        members_begin_insert_rows,
        members_end_insert_rows,
        members_begin_move_rows,
        members_end_move_rows,
        members_begin_remove_rows,
        members_end_remove_rows,
        members_new_typing_indicator,
    } = ptr_bundle;
    let members_emit = MembersEmitter {
        qobject: Arc::new(AtomicPtr::new(members)),
        filter_changed: members_filter_changed,
        filter_regex_changed: members_filter_regex_changed,
        new_data_ready: members_new_data_ready,
        new_typing_indicator: members_new_typing_indicator,
    };
    let model = MembersList {
        qobject: members,
        layout_about_to_be_changed: members_layout_about_to_be_changed,
        layout_changed: members_layout_changed,
        data_changed: members_data_changed,
        begin_reset_model: members_begin_reset_model,
        end_reset_model: members_end_reset_model,
        begin_insert_rows: members_begin_insert_rows,
        end_insert_rows: members_end_insert_rows,
        begin_move_rows: members_begin_move_rows,
        end_move_rows: members_end_move_rows,
        begin_remove_rows: members_begin_remove_rows,
        end_remove_rows: members_end_remove_rows,
    };
    let d_members = Members::new(members_emit, model);
    d_members
}

#[no_mangle]
pub unsafe extern "C" fn members_free(ptr: *mut Members) {
    Box::from_raw(ptr).emit().clear();
}

#[no_mangle]
pub unsafe extern "C" fn members_add_to_conversation(
    ptr: *mut Members,
    id_str: *const c_ushort,
    id_len: c_int,
) -> bool {
    let obj = &mut *ptr;
    let mut id = String::new();
    set_string_from_utf16(&mut id, id_str, id_len);
    obj.add_to_conversation(id)
}

#[no_mangle]
pub unsafe extern "C" fn members_remove_from_conversation_by_index(
    ptr: *mut Members,
    row_index: u64,
) -> bool {
    let obj = &mut *ptr;
    obj.remove_from_conversation_by_index(row_index)
}

#[no_mangle]
pub unsafe extern "C" fn members_toggle_filter_regex(ptr: *mut Members) -> bool {
    let obj = &mut *ptr;
    obj.toggle_filter_regex()
}

#[no_mangle]
pub unsafe extern "C" fn members_typing_members(
    ptr: *const Members,
    data: *mut QString,
    set: fn(*mut QString, str_: *const c_char, len: c_int),
) {
    let obj = &*ptr;
    let ret = obj.typing_members();
    let str_: *const c_char = ret.as_ptr() as (*const c_char);
    set(data, str_, ret.len() as i32);
}

#[no_mangle]
pub unsafe extern "C" fn members_filter_get(
    ptr: *const Members,
    prop: *mut QString,
    set: fn(*mut QString, *const c_char, c_int),
) {
    let obj = &*ptr;
    let value = obj.filter();
    let str_: *const c_char = value.as_ptr() as *const c_char;
    set(prop, str_, to_c_int(value.len()));
}

#[no_mangle]
pub unsafe extern "C" fn members_filter_set(
    ptr: *mut Members,
    value: *const c_ushort,
    len: c_int,
) {
    let obj = &mut *ptr;
    let mut s = String::new();
    set_string_from_utf16(&mut s, value, len);
    obj.set_filter(s);
}

#[no_mangle]
pub unsafe extern "C" fn members_filter_regex_get(ptr: *const Members) -> bool {
    (&*ptr).filter_regex()
}

#[no_mangle]
pub unsafe extern "C" fn members_filter_regex_set(
    ptr: *mut Members,
    value: bool,
) {
    (&mut *ptr).set_filter_regex(value)
}

#[no_mangle]
pub unsafe extern "C" fn members_row_count(ptr: *const Members) -> c_int {
    to_c_int((&*ptr).row_count())
}

#[no_mangle]
pub unsafe extern "C" fn members_insert_rows(
    ptr: *mut Members,
    row: c_int,
    count: c_int,
) -> bool {
    match (to_usize(row), to_usize(count)) {
        (Some(row), Some(count)) => (&mut *ptr).insert_rows(row, count),
        _ => false,
    }
}

#[no_mangle]
pub unsafe extern "C" fn members_remove_rows(
    ptr: *mut Members,
    row: c_int,
    count: c_int,
) -> bool {
    match (to_usize(row), to_usize(count)) {
        (Some(row), Some(count)) => (&mut *ptr).remove_rows(row, count),
        _ => false,
    }
}

#[no_mangle]
pub unsafe extern "C" fn members_can_fetch_more(ptr: *const Members) -> bool {
    (&*ptr).can_fetch_more()
}

#[no_mangle]
pub unsafe extern "C" fn members_fetch_more(ptr: *mut Members) {
    (&mut *ptr).fetch_more()
}

#[no_mangle]
pub unsafe extern "C" fn members_sort(
    ptr: *mut Members,
    column: u8,
    order: SortOrder,
) {
    (&mut *ptr).sort(column, order)
}

#[no_mangle]
pub unsafe extern "C" fn members_data_last_typing(
    ptr: *const Members,
    row: c_int,
) -> COption<i64> {
    let obj = &*ptr;
    obj.last_typing(to_usize(row).unwrap_or(0)).into()
}

#[no_mangle]
pub unsafe extern "C" fn members_data_matched(
    ptr: *const Members,
    row: c_int,
) -> bool {
    let obj = &*ptr;
    obj.matched(to_usize(row).unwrap_or(0))
}

#[no_mangle]
pub unsafe extern "C" fn members_data_user_id(
    ptr: *const Members,
    row: c_int,
    d: *mut QString,
    set: fn(*mut QString, *const c_char, len: c_int),
) {
    let obj = &*ptr;
    let data = obj.user_id(to_usize(row).unwrap_or(0));
    let str_: *const c_char = data.as_ptr() as *const c_char;
    set(d, str_, to_c_int(data.len()));
}

#[derive(Clone, Copy)]
#[repr(C)]
pub struct MembersPtrBundle {
    members: *mut MembersQObject,
    members_filter_changed: fn(*mut MembersQObject),
    members_filter_regex_changed: fn(*mut MembersQObject),
    members_new_data_ready: fn(*mut MembersQObject),
    members_layout_about_to_be_changed: fn(*mut MembersQObject),
    members_layout_changed: fn(*mut MembersQObject),
    members_data_changed: fn(*mut MembersQObject, usize, usize),
    members_begin_reset_model: fn(*mut MembersQObject),
    members_end_reset_model: fn(*mut MembersQObject),
    members_begin_insert_rows: fn(*mut MembersQObject, usize, usize),
    members_end_insert_rows: fn(*mut MembersQObject),
    members_begin_move_rows: fn(*mut MembersQObject, usize, usize, usize),
    members_end_move_rows: fn(*mut MembersQObject),
    members_begin_remove_rows: fn(*mut MembersQObject, usize, usize),
    members_end_remove_rows: fn(*mut MembersQObject),
    members_new_typing_indicator: fn(*mut MembersQObject),
}
